#ifndef __server_hpp__
#define __server_hpp__

#pragma once

#include <set>
#include <deque>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/function.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem/fstream.hpp>

#include <cm/strings.hpp>
#include <cm/logger.hpp>
#include <cm/filesystem.hpp>

#include <cm/client_server_01/common.hpp>
#include <cm/client_server_01/connections_mngr.hpp>

// LAST_LOG_ID_USED			6

using boost::asio::ip::tcp;

namespace cm {
namespace client_server {

//----------------------------------------------------------------------

template <typename verbs_reader>
class connection : public connection_base
{
public:
	typedef boost::shared_ptr<connection> connection_ptr;
public:
	connection(boost::asio::io_service& io_service,
			connections_manager& connections_manager,
				const cm::client_server::session_id& s_id,
					boost::program_options::variables_map& vm)
		: connection_base(io_service,connections_manager,s_id),
			vm_(vm),
				verbs_reader_(vm,connection_handlers_)
	{
	}
	virtual ~connection()
	{
	}
private:// verbs
	virtual void read_verb()
	{
		verbs_reader_.read_verb();
	}
private:// verbs
	verbs_reader							verbs_reader_;
	boost::program_options::variables_map&	vm_;
};

//----------------------------------------------------------------------

template<typename Proto>
class server
{
public:
	server(boost::asio::io_service& io_service,
		const tcp::endpoint& endpoint)
		: io_service_(io_service),
			acceptor_(io_service, endpoint),
				connection_id_counter_(server_session_id)
	{
		boost::program_options::options_description config_file_options;
		config_file_options.add(Proto::init_options());

		// Get executable path and split file name
		boost::filesystem::path exe_path = cm::filesystem::exe_path<boost::filesystem::path>();
		std::string cfn_file_name = exe_path.leaf();
		exe_path.remove_leaf();

		std::string::size_type pos = cfn_file_name.find_last_of(boost::filesystem::dot<char>::value);
		if (pos != std::string::npos)
		{
			cfn_file_name = cfn_file_name.substr(0,pos);
		}

		// Replance exe extention with "cfg"
		cfn_file_name += boost::filesystem::dot<char>::value;
		cfn_file_name.append("cfg");

		// Open if exist the configuration file
		boost::filesystem::ifstream	cfn_file(exe_path / cfn_file_name);
		boost::program_options::store(boost::program_options::parse_config_file(cfn_file,config_file_options),vm_);
		boost::program_options::notify(vm_);

		// Start first connection
		connection<Proto>::connection_ptr new_connection(new connection<Proto>(io_service_,connections_manager_,++connection_id_counter_,vm_));
	
		// Start listening acceptor
		acceptor_.async_accept(new_connection->socket(),
			boost::bind(&server::handle_accept, this, new_connection,
			boost::asio::placeholders::error));				
	}
	~server()
	{
		Logger::instance() << cm::startl << 2 << cm::delim
			<< "cm::client_server::server::~server" << cm::endl;
	}
private:
	void do_stop()
	{
		Logger::instance() << cm::startl << 4 << cm::delim
			<< "cm::client_server::server::do_stop" << cm::endl;

		connections_manager_.stop_all();

		// The server is stopped by cancelling all outstanding asynchronous
		// operations. Once all operations have finished the io_service::run() call
		// will exit.
		acceptor_.close();

		Logger::instance() << cm::startl << 5 << cm::delim
			<< "cm::client_server::server::do_stop" << cm::endl;
	}
public:
	void stop()
	{
		Logger::instance() << cm::startl << 3 << cm::delim
			<< "cm::client_server::server::stop" << cm::endl;

		// Post a call to the stop function so that server::stop() is safe to call
		// from any thread.
		io_service_.post(boost::bind(&server::do_stop, this));
	}
private:
	void handle_accept(typename connection<Proto>::connection_ptr accepted_connection,const boost::system::error_code& error)
	{
		if (!error)
		{
			Logger::instance() << cm::startl << 6 << cm::delim
				<< "cm::client_server::server::handle_accept" << cm::delim
					<< "Connessione accettata: " << accepted_connection->connection_id_ << cm::endl;

			connections_manager_.start(accepted_connection);

			// Start a new connection
			connection<Proto>::connection_ptr new_connection(new connection<Proto>(io_service_,connections_manager_,++connection_id_counter_,vm_));

			acceptor_.async_accept(new_connection->socket(),
				boost::bind(&server::handle_accept,this,new_connection,boost::asio::placeholders::error));
		}
	}
private:
	boost::asio::io_service&		io_service_;
	tcp::acceptor					acceptor_;
public:
	connections_manager				connections_manager_;
private:
	cm::client_server::session_id	connection_id_counter_;
private:
	boost::program_options::variables_map	vm_;
};
}}

#endif